package messages

// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.4.1

import (
	
	
	
	
	

	
	
	
	
	
	
	
	
	
	
	
	
	
)

type marshalKDCReq struct {
	PVNO    int                  `asn1:"explicit,tag:1"`
	MsgType int                  `asn1:"explicit,tag:2"`
	PAData  types.PADataSequence `asn1:"explicit,optional,tag:3"`
	ReqBody asn1.RawValue        `asn1:"explicit,tag:4"`
}

// KDCReqFields represents the KRB_KDC_REQ fields.
type KDCReqFields struct {
	PVNO    int
	MsgType int
	PAData  types.PADataSequence
	ReqBody KDCReqBody
	Renewal bool
}

// ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
type ASReq struct {
	KDCReqFields
}

// TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
type TGSReq struct {
	KDCReqFields
}

type marshalKDCReqBody struct {
	KDCOptions  asn1.BitString      `asn1:"explicit,tag:0"`
	CName       types.PrincipalName `asn1:"explicit,optional,tag:1"`
	Realm       string              `asn1:"generalstring,explicit,tag:2"`
	SName       types.PrincipalName `asn1:"explicit,optional,tag:3"`
	From        time.Time           `asn1:"generalized,explicit,optional,tag:4"`
	Till        time.Time           `asn1:"generalized,explicit,tag:5"`
	RTime       time.Time           `asn1:"generalized,explicit,optional,tag:6"`
	Nonce       int                 `asn1:"explicit,tag:7"`
	EType       []int32             `asn1:"explicit,tag:8"`
	Addresses   []types.HostAddress `asn1:"explicit,optional,tag:9"`
	EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"`
	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
	AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"`
}

// KDCReqBody implements the KRB_KDC_REQ request body.
type KDCReqBody struct {
	KDCOptions        asn1.BitString      `asn1:"explicit,tag:0"`
	CName             types.PrincipalName `asn1:"explicit,optional,tag:1"`
	Realm             string              `asn1:"generalstring,explicit,tag:2"`
	SName             types.PrincipalName `asn1:"explicit,optional,tag:3"`
	From              time.Time           `asn1:"generalized,explicit,optional,tag:4"`
	Till              time.Time           `asn1:"generalized,explicit,tag:5"`
	RTime             time.Time           `asn1:"generalized,explicit,optional,tag:6"`
	Nonce             int                 `asn1:"explicit,tag:7"`
	EType             []int32             `asn1:"explicit,tag:8"`
	Addresses         []types.HostAddress `asn1:"explicit,optional,tag:9"`
	EncAuthData       types.EncryptedData `asn1:"explicit,optional,tag:10"`
	AdditionalTickets []Ticket            `asn1:"explicit,optional,tag:11"`
}

// NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request.
func ( string,  *config.Config,  types.PrincipalName) (ASReq, error) {
	 := types.PrincipalName{
		NameType:   nametype.KRB_NT_SRV_INST,
		NameString: []string{"krbtgt", },
	}
	return NewASReq(, , , )
}

// NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request.
func ( string,  *config.Config,  types.PrincipalName) (ASReq, error) {
	 := types.PrincipalName{
		NameType:   nametype.KRB_NT_PRINCIPAL,
		NameString: []string{"kadmin", "changepw"},
	}
	return NewASReq(, , , )
}

// NewASReq generates a new KRB_AS_REQ struct for a given SNAME.
func ( string,  *config.Config, ,  types.PrincipalName) (ASReq, error) {
	,  := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
	if  != nil {
		return ASReq{}, 
	}
	 := time.Now().UTC()
	// Copy the default options to make this thread safe
	 := types.NewKrbFlags()
	copy(.Bytes, .LibDefaults.KDCDefaultOptions.Bytes)
	.BitLength = .LibDefaults.KDCDefaultOptions.BitLength
	 := ASReq{
		KDCReqFields{
			PVNO:    iana.PVNO,
			MsgType: msgtype.KRB_AS_REQ,
			PAData:  types.PADataSequence{},
			ReqBody: KDCReqBody{
				KDCOptions: ,
				Realm:      ,
				CName:      ,
				SName:      ,
				Till:       .Add(.LibDefaults.TicketLifetime),
				Nonce:      int(.Int64()),
				EType:      .LibDefaults.DefaultTktEnctypeIDs,
			},
		},
	}
	if .LibDefaults.Forwardable {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Forwardable)
	}
	if .LibDefaults.Canonicalize {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Canonicalize)
	}
	if .LibDefaults.Proxiable {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Proxiable)
	}
	if .LibDefaults.RenewLifetime != 0 {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Renewable)
		.ReqBody.RTime = .Add(.LibDefaults.RenewLifetime)
		.ReqBody.RTime = .Add(time.Duration(48) * time.Hour)
	}
	if !.LibDefaults.NoAddresses {
		,  := types.LocalHostAddresses()
		if  != nil {
			return , fmt.Errorf("could not get local addresses: %v", )
		}
		 = append(, types.HostAddressesFromNetIPs(.LibDefaults.ExtraAddresses)...)
		.ReqBody.Addresses = 
	}
	return , nil
}

// NewTGSReq generates a new KRB_TGS_REQ struct.
func ( types.PrincipalName,  string,  *config.Config,  Ticket,  types.EncryptionKey,  types.PrincipalName,  bool) (TGSReq, error) {
	,  := tgsReq(, , , , )
	if  != nil {
		return , 
	}
	 = .setPAData(, )
	return , 
}

// NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7)
func ( types.PrincipalName,  string,  *config.Config,  Ticket,  types.EncryptionKey,  types.PrincipalName,  bool,  Ticket) (TGSReq, error) {
	,  := tgsReq(, , , , )
	if  != nil {
		return , 
	}
	.ReqBody.AdditionalTickets = []Ticket{}
	types.SetFlag(&.ReqBody.KDCOptions, flags.EncTktInSkey)
	 = .setPAData(, )
	return , 
}

// tgsReq populates the fields for a TGS_REQ
func tgsReq(,  types.PrincipalName,  string,  bool,  *config.Config) (TGSReq, error) {
	,  := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
	if  != nil {
		return TGSReq{}, 
	}
	 := time.Now().UTC()
	 := KDCReqFields{
		PVNO:    iana.PVNO,
		MsgType: msgtype.KRB_TGS_REQ,
		ReqBody: KDCReqBody{
			KDCOptions: types.NewKrbFlags(),
			Realm:      ,
			CName:      , // Add the CName to make validation of the reply easier
			SName:      ,
			Till:       .Add(.LibDefaults.TicketLifetime),
			Nonce:      int(.Int64()),
			EType:      .LibDefaults.DefaultTGSEnctypeIDs,
		},
		Renewal: ,
	}
	if .LibDefaults.Forwardable {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Forwardable)
	}
	if .LibDefaults.Canonicalize {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Canonicalize)
	}
	if .LibDefaults.Proxiable {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Proxiable)
	}
	if .LibDefaults.RenewLifetime > time.Duration(0) {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Renewable)
		.ReqBody.RTime = .Add(.LibDefaults.RenewLifetime)
	}
	if !.LibDefaults.NoAddresses {
		,  := types.LocalHostAddresses()
		if  != nil {
			return TGSReq{}, fmt.Errorf("could not get local addresses: %v", )
		}
		 = append(, types.HostAddressesFromNetIPs(.LibDefaults.ExtraAddresses)...)
		.ReqBody.Addresses = 
	}
	if  {
		types.SetFlag(&.ReqBody.KDCOptions, flags.Renew)
		types.SetFlag(&.ReqBody.KDCOptions, flags.Renewable)
	}
	return TGSReq{
		,
	}, nil
}

func ( *TGSReq) ( Ticket,  types.EncryptionKey) error {
	// Marshal the request and calculate checksum
	,  := .ReqBody.Marshal()
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error marshaling TGS_REQ body")
	}
	,  := crypto.GetEtype(.KeyType)
	if  != nil {
		return krberror.Errorf(, krberror.EncryptingError, "error getting etype to encrypt authenticator")
	}
	,  := .GetChecksumHash(.KeyValue, , keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM)
	if  != nil {
		return krberror.Errorf(, krberror.ChksumError, "error getting etype checksum hash")
	}

	// Form PAData for TGS_REQ
	// Create authenticator
	,  := types.NewAuthenticator(.Realm, .ReqBody.CName)
	if  != nil {
		return krberror.Errorf(, krberror.KRBMsgError, "error generating new authenticator")
	}
	.Cksum = types.Checksum{
		CksumType: .GetHashID(),
		Checksum:  ,
	}
	// Create AP_REQ
	,  := NewAPReq(, , )
	if  != nil {
		return krberror.Errorf(, krberror.KRBMsgError, "error generating new AP_REQ")
	}
	,  := .Marshal()
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data")
	}
	.PAData = types.PADataSequence{
		types.PAData{
			PADataType:  patype.PA_TGS_REQ,
			PADataValue: ,
		},
	}
	return nil
}

// Unmarshal bytes b into the ASReq struct.
func ( *ASReq) ( []byte) error {
	var  marshalKDCReq
	,  := asn1.UnmarshalWithParams(, &, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ))
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error unmarshaling AS_REQ")
	}
	 := msgtype.KRB_AS_REQ
	if .MsgType !=  {
		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", , .MsgType)
	}
	var  KDCReqBody
	 = .Unmarshal(.ReqBody.Bytes)
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error processing AS_REQ body")
	}
	.MsgType = .MsgType
	.PAData = .PAData
	.PVNO = .PVNO
	.ReqBody = 
	return nil
}

// Unmarshal bytes b into the TGSReq struct.
func ( *TGSReq) ( []byte) error {
	var  marshalKDCReq
	,  := asn1.UnmarshalWithParams(, &, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ))
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error unmarshaling TGS_REQ")
	}
	 := msgtype.KRB_TGS_REQ
	if .MsgType !=  {
		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", , .MsgType)
	}
	var  KDCReqBody
	 = .Unmarshal(.ReqBody.Bytes)
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error processing TGS_REQ body")
	}
	.MsgType = .MsgType
	.PAData = .PAData
	.PVNO = .PVNO
	.ReqBody = 
	return nil
}

// Unmarshal bytes b into the KRB_KDC_REQ body struct.
func ( *KDCReqBody) ( []byte) error {
	var  marshalKDCReqBody
	,  := asn1.Unmarshal(, &)
	if  != nil {
		return krberror.Errorf(, krberror.EncodingError, "error unmarshaling KDC_REQ body")
	}
	.KDCOptions = .KDCOptions
	if len(.KDCOptions.Bytes) < 4 {
		 := make([]byte, 4-len(.KDCOptions.Bytes))
		.KDCOptions.Bytes = append(, .KDCOptions.Bytes...)
		.KDCOptions.BitLength = len(.KDCOptions.Bytes) * 8
	}
	.CName = .CName
	.Realm = .Realm
	.SName = .SName
	.From = .From
	.Till = .Till
	.RTime = .RTime
	.Nonce = .Nonce
	.EType = .EType
	.Addresses = .Addresses
	.EncAuthData = .EncAuthData
	if len(.AdditionalTickets.Bytes) > 0 {
		.AdditionalTickets,  = unmarshalTicketsSequence(.AdditionalTickets)
		if  != nil {
			return krberror.Errorf(, krberror.EncodingError, "error unmarshaling additional tickets")
		}
	}
	return nil
}

// Marshal ASReq struct.
func ( *ASReq) () ([]byte, error) {
	 := marshalKDCReq{
		PVNO:    .PVNO,
		MsgType: .MsgType,
		PAData:  .PAData,
	}
	,  := .ReqBody.Marshal()
	if  != nil {
		var  []byte
		return , 
	}
	.ReqBody = asn1.RawValue{
		Class:      asn1.ClassContextSpecific,
		IsCompound: true,
		Tag:        4,
		Bytes:      ,
	}
	,  := asn1.Marshal()
	if  != nil {
		return , krberror.Errorf(, krberror.EncodingError, "error marshaling AS_REQ")
	}
	 = asn1tools.AddASNAppTag(, asnAppTag.ASREQ)
	return , nil
}

// Marshal TGSReq struct.
func ( *TGSReq) () ([]byte, error) {
	 := marshalKDCReq{
		PVNO:    .PVNO,
		MsgType: .MsgType,
		PAData:  .PAData,
	}
	,  := .ReqBody.Marshal()
	if  != nil {
		var  []byte
		return , 
	}
	.ReqBody = asn1.RawValue{
		Class:      asn1.ClassContextSpecific,
		IsCompound: true,
		Tag:        4,
		Bytes:      ,
	}
	,  := asn1.Marshal()
	if  != nil {
		return , krberror.Errorf(, krberror.EncodingError, "error marshaling AS_REQ")
	}
	 = asn1tools.AddASNAppTag(, asnAppTag.TGSREQ)
	return , nil
}

// Marshal KRB_KDC_REQ body struct.
func ( *KDCReqBody) () ([]byte, error) {
	var  []byte
	 := marshalKDCReqBody{
		KDCOptions:  .KDCOptions,
		CName:       .CName,
		Realm:       .Realm,
		SName:       .SName,
		From:        .From,
		Till:        .Till,
		RTime:       .RTime,
		Nonce:       .Nonce,
		EType:       .EType,
		Addresses:   .Addresses,
		EncAuthData: .EncAuthData,
	}
	,  := MarshalTicketSequence(.AdditionalTickets)
	if  != nil {
		return , krberror.Errorf(, krberror.EncodingError, "error in marshaling KDC request body additional tickets")
	}
	//The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody
	.Tag = 11
	if len(.Bytes) > 0 {
		.AdditionalTickets = 
	}
	,  = asn1.Marshal()
	if  != nil {
		return , krberror.Errorf(, krberror.EncodingError, "error in marshaling KDC request body")
	}
	return , nil
}